<?php
/**
 * @file mysql.class.php
 * @author 禅元天道 chanyuantiandao@126.com
 * @DateTime 2022-01-17 15:01
 * @brief mysql数据库的pdo连接类
 */

!defined('CHAN_CMS') && exit('非法访问！');

class DB implements Database
{

    private static $_instance = null;
    private $pdoLink = null;
    private $pdoStatement = null;

    private $config = array();

    public static function getInstance(array $config): Database
    {
        if(is_null(self::$_instance) || !self::$_instance instanceof self){
            self::$_instance = new self($config);
        }
        return self::$_instance;
    }

    public function getFieldsInfoOfTable(string $table): array
    {
        $sql = 'SELECT COLUMN_NAME name, COLUMN_TYPE type, COLUMN_KEY indexType, EXTRA autoIncrement, COLUMN_DEFAULT defVal, IF(IS_NULLABLE=\'NO\', true, false) AS \'isMust\' FROM INFORMATION_SCHEMA.COLUMNS where table_schema =\''.$this->config['dbname'].'\' AND table_name = \'#@__'.$table.'\'';
        if($this->query($sql)) {
            $result = array();
            while ($arr = $this->pdoStatement->fetch(PDO::FETCH_ASSOC)) {
                array_push($result, $arr);
            }
            return $result;
        }else {
            throw new ChanException('获取表数据结构时发生错误！');
        }
    }

    public function beginTransaction(bool $flag = true) : bool
    {
        return $this->pdoLink->beginTransaction();
    }

    public function commit() : bool
    {
        return $this->pdoLink->commit();
    }

    public function rollback() : bool
    {
        return $this->pdoLink->rollBack();
    }

    public function insert(string $table, array $data, bool $isReplace = false): bool
    {
        if(empty($data) || !is_array($data) || (array_keys($data) === range(0, count($data) - 1))){
            return false;
        }
        $sql = $isReplace ? 'REPLACE' : 'INSERT';
        $sql .= ' INTO `#@__'.$table.'` ';
        $_field = $_value = '';
        foreach ($data as $k => $v) {
            $_field .= $_field == '' ? '`'.$k.'`' : ',`'.$k.'`';
            $_value .= $_value == '' ? '\''.$v.'\'' : ',\''.$v.'\'';
        }
        $sql .= '('.$_field.') VALUES ('.$_value.')';

        return $this->query($sql);
    }

    public function getLastInsertId(): int
    {
        return $this->pdoLink->lastInsertId();
    }


    public function select(string $table, string $fields, array $where, array $orderBy, array $limit): bool
    {
        //缓存不可用时读取数据库
        $sql = 'SELECT '.$fields.' FROM #@__'.$table;
        if(!empty($where)){
            $_sql = $sql .= ' WHERE ';
            foreach ($where as $w) {
                $sql .= ($sql == $_sql) ? '`'.$w[0].'` '.$w[2].' \''.$w[1].'\'' : ' AND `'.$w[0].'` '.$w[2].' \''.$w[1].'\'';
            }
        }
        if(!empty($orderBy)){
            $_sql = $sql .= ' ORDER BY ';
            foreach ($orderBy as $key => $order){
                $sql .= ($sql == $_sql) ? '`'.$key.'` '.strtoupper($order) : ', `'.$key.'` '.strtoupper($order);
            }
        }
        if(!empty($limit)){
            $sql .= ' LIMIT '.$limit[0].', '.$limit[1];
        }
        return $this->query($sql);
    }

    public function update(string $table, array $data, array $where): bool
    {
        $_sql = $sql = 'UPDATE `#@__'.$table.'` SET ';
        $_field = $_value = '';
        foreach ($data as $uk => $uv) {
            $sql .= ($sql == $_sql) ? '`'.$uk.'` = \''.$uv.'\'' : ',`'.$uk.'` = \''.$uv.'\'';
        }
        $_sql = $sql = $sql.' WHERE ';
        foreach ($where as $w) {
            $sql .= ($sql == $_sql) ? '`'.$w[0].'` '.$w[2].' \''.$w[1].'\'' : ' AND `'.$w[0].'` '.$w[2].' \''.$w[1].'\'';
        }
        return $this->query($sql);
    }

    public function delete(string $table, array $where): bool
    {
        $_sql = $sql = 'DELETE FROM #@__'.$table.' WHERE ';
        foreach ($where as $w) {
            $sql .= ($sql == $_sql) ? '`'.$w[0].'` '.$w[2].' \''.$w[1].'\'' : ' AND `'.$w[0].'` '.$w[2].' \''.$w[1].'\'';
        }
        return $this->query($sql);
    }

    public function fetchData() : array{
        return $this->pdoStatement->fetchAll();
    }
    public function getAffectedRow(): int
    {
        return $this->pdoStatement->rowCount();
    }

    public function getLastError(): array
    {
        return array(
            'errorCode' => $this->link->errorCode(),
            'errorInfo' => $this->link->errorInfo()[2]
        );
    }


    public function getLastSql(): string
    {
        return $this->pdoStatement->queryString;
    }

    private function query(string $sql, array $params = null){

        $sql = str_replace('#@__', $this->config['table_pre'], $sql);
        $this->pdoStatement = $this->pdoLink->prepare($sql);

        if($params != null){
            foreach ($params as $index=>$val){
                $this->pdoStatement->bindParam($index, $val);
            }
        }
        if($this->pdoStatement->execute()){
            return true;
        }else{
            return false;
        }
    }

    private function connect($config){
        $dsn = 'mysql:host='.$config['host'].';port='.$config['port'].';dbname='.$config['dbname'].';charset='.$config['charset'];
        try {
            $this->pdoLink = new PDO($dsn,$config['uname'], $config['upass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8")); //保持长连接
            $this->pdoLink->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
        } catch (PDOException $e) {
            throw new ChanException('数据库连接失败：'.$e->getMessage());
        }
        return true;
    }

    private function __construct(array $config){
        if(!class_exists('PDO'))
        {
            throw new ChanException('尚未开启PDO支持库！');
        }
        $defaultConfig = array(
            'host' => '127.0.0.1',
            'port' => 3306,
            'type' => 'mysql',
            'uname' => 'root',
            'upass' => '123456',
            'dbname' => 'caedoc',
            'table_pre' => 'cae_',
            'charset' => 'utf8',
            'cache_expire_time' => 3600
        );
        $this->config = array_merge($defaultConfig, $config);
        $this->connect($config);
    }

    function __destruct() {
        $this->link = null;
        $this->result = null;
        $this->config = array();
        self::$_instance = null;
    }

    private function __clone(){}


}